﻿// =============================================================================
// TNTTools GetterMethod.cs
// Copyright (c) 2013 TNTTechnologies Inc.
// =============================================================================

#region

using System;
using System.Linq.Expressions;
using System.Reflection;

#endregion

namespace TNTTools.Reflection{
    public class GetterMethod{
        public bool IsNullable = false;

        public Func<object, Boolean> boolGetter;
        public Func<object, Byte> byteGetter;
        public Func<object, Char> charGetter;
        public Func<object, DateTime> dateTimeGetter;
        public Func<object, Decimal> decimalGetter;
        public Func<object, Double> doubleGetter;
        public Func<object, Int16> int16Getter;
        public Func<object, Int32> int32Getter;
        public Func<object, Int64> int64Getter;
        public Func<object, object> objectGetter; // default always works but then your boxing and the ToString is slow.
        public Func<object, SByte> sbyteGetter;
        public Func<object, Single> singleGetter;
        public Func<object, String> stringGetter;
        public Func<object, UInt32> uInt32Getter;
        public Func<object, UInt16> uint16Getter;
        public Func<object, UInt64> uint64Getter;

        /// <summary>
        ///     up to 100x faster than reflection's GetValue
        /// </summary>
        /// <param name="info"></param>
        /// <param name="source"></param>
        /// <returns></returns>
        public GetterMethod(PropertyInfo info, object source /*, IBasicTypes IbasicTypes*/){
            if (info.PropertyType.IsGenericType && info.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)){
                this.IsNullable = true;
            }

            //if (IbasicTypes.GetBasicTypes().Contains(info.PropertyType))
            {
                this.boolGetter = this.GetTypedGetter<bool>(info);
                this.byteGetter = this.GetTypedGetter<byte>(info);
                this.sbyteGetter = this.GetTypedGetter<sbyte>(info);
                this.charGetter = this.GetTypedGetter<char>(info);
                this.decimalGetter = this.GetTypedGetter<decimal>(info);
                this.doubleGetter = this.GetTypedGetter<double>(info);
                this.singleGetter = this.GetTypedGetter<Single>(info);
                this.int32Getter = this.GetTypedGetter<Int32>(info);
                this.uInt32Getter = this.GetTypedGetter<UInt32>(info);
                this.int64Getter = this.GetTypedGetter<Int64>(info);
                this.uint64Getter = this.GetTypedGetter<UInt64>(info);
                this.int16Getter = this.GetTypedGetter<Int16>(info);
                this.uint16Getter = this.GetTypedGetter<UInt16>(info);
                this.stringGetter = this.GetTypedGetter<String>(info);
                //TODO: test which getter is faster object or string .tostring()
                this.dateTimeGetter = this.GetTypedGetter<DateTime>(info);
            }
            //else
            {
                this.objectGetter = this.GetTypedGetter<object>(info);
            }
        }

        /// <summary>
        ///     Prevent boxing unboxing.
        /// </summary>
        /// <param name="info"></param>
        /// <param name="obj"></param>
        /// <returns></returns>
        public string GetStringValue(ReflectiveProperty.QuickType type, object obj){
            if (this.IsNullable){
                // This prevents an exception from being thrown if the property property throws an exception.
                // return objectGetter(obj).ToString();
            }
            if (type == ReflectiveProperty.QuickType.Boolean){
                return this.boolGetter(obj).ToString();
            }
            if (type == ReflectiveProperty.QuickType.Byte){
                return this.byteGetter(obj).ToString();
            }
            if (type == ReflectiveProperty.QuickType.SByte){
                return this.sbyteGetter(obj).ToString();
            }
            if (type == ReflectiveProperty.QuickType.Char){
                return this.charGetter(obj).ToString();
            }
            if (type == ReflectiveProperty.QuickType.Decimal){
                return this.decimalGetter(obj).ToString();
            }
            if (type == ReflectiveProperty.QuickType.Double){
                return this.doubleGetter(obj).ToString();
            }
            if (type == ReflectiveProperty.QuickType.Single){
                return this.singleGetter(obj).ToString();
            }
            if (type == ReflectiveProperty.QuickType.Int32){
                return this.int32Getter(obj).ToString();
            }
            if (type == ReflectiveProperty.QuickType.UInt32){
                return this.uInt32Getter(obj).ToString();
            }
            if (type == ReflectiveProperty.QuickType.Int64){
                return this.int64Getter(obj).ToString();
            }
            if (type == ReflectiveProperty.QuickType.UInt64){
                return this.uint64Getter(obj).ToString();
            }
            if (type == ReflectiveProperty.QuickType.Int16){
                return this.int16Getter(obj).ToString();
            }
            if (type == ReflectiveProperty.QuickType.UInt16){
                return this.uint16Getter(obj).ToString();
            }
            if (type == ReflectiveProperty.QuickType.String){
                return this.stringGetter(obj); // May not be faster
            }
            if (type == ReflectiveProperty.QuickType.DateTime){
                return this.dateTimeGetter(obj).ToString();
            }
            //TODO: All value types

            return this.objectGetter(obj).ToString();
        }

        /// <summary>
        ///     Builds a lamda to access a getter rather than using reflection's GetValue
        ///     Note: We could always use a cast to object but then we box value types ie Int's DateTime(struct)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="info"></param>
        /// <returns></returns>
        private Func<object, T> GetTypedGetter<T>(PropertyInfo info){
            //bool isbasic = IbasicTypes.GetBasicTypes().Contains(info.PropertyType);

            if (info.PropertyType == typeof(T) || typeof(T) == typeof(object)){
                var instance = Expression.Parameter(typeof(object), "instance");
                var instanceCast = info.GetGetMethod(true).IsStatic
                                       ? null
                                       : Expression.Convert(instance, info.ReflectedType);
                var propertyAccess = Expression.Property(instanceCast, info);

                var castPropertyValue = Expression.Convert(propertyAccess, typeof(T));
                var lambda = Expression.Lambda<Func<object, T>>(castPropertyValue, instance);
                return lambda.Compile();
            }

            return null;
        }
    }
}